package com.xqc.context;

import com.xqc.annotation.*;
import com.xqc.aware.XqcBeanAware;
import com.xqc.aware.XqcBeanPostProcessor;
import com.xqc.aware.XqcInitializingBean;
import com.xqc.definition.XqcBeanDefinition;
import com.xqc.enums.ScopeEnum;
import com.xqc.exception.ThrowUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

public class XqcApplicationContext {

    //bean定义
    private static final Map<String, XqcBeanDefinition> beanDefinitions = new HashMap<>();
    //单例bean池
    private static final Map<String,Object> singletonObjects = new HashMap<>();
    //bean后置处理器
    private static final List<XqcBeanPostProcessor> beanPostProcessors = new LinkedList<>();

    public XqcApplicationContext(Class<?> clazz){
        if (!clazz.isAnnotationPresent(XqcConfiguration.class)) {
            ThrowUtils.throwEx("扫描配置不正确");
        }
        //1、解析入口配置类,生成扫描路径
        String packagePath = getPackagePath(clazz);
        //2、扫描包,生成beanDefinitions
        scan(packagePath);
        //3、解析和生成bean
        this.refresh();
    }

    private void refresh(){
        for (Map.Entry<String, XqcBeanDefinition> entry : beanDefinitions.entrySet()) {
            String beanName = entry.getKey();
            XqcBeanDefinition xqcBeanDefinition = entry.getValue();
            if (ScopeEnum.PROTOTYPE.getCode().equals(xqcBeanDefinition.getScope())) {
                continue;
            }
            //实例化，生成bean对象
            Object bean = createBean(beanName, xqcBeanDefinition);
            //放入map
            singletonObjects.put(beanName,bean);
        }
    }

    /**
     * 生成beanDefinitions
     * @param packagePath
     */
    private void scan(String packagePath) {
        //2、根据路径扫描文件，获取class
        List<String> classes = new ArrayList<>();
        scannerPackage(classes,packagePath);
        for (String class1 : classes) {
            try {
                //加载成class方便后续判断
                Class<?> aClass = XqcApplicationContext.class.getClassLoader().loadClass(subPath(class1));
                //是否有XqcComponent注解
                if(!aClass.isAnnotationPresent(XqcComponent.class)){
                    continue;
                }
                //是否实现XqcBeanPostProcessor接口
                if(XqcBeanPostProcessor.class.isAssignableFrom(aClass)){
                    Object instance = aClass.getConstructor().newInstance();
                    beanPostProcessors.add((XqcBeanPostProcessor) instance);
                }
                XqcBeanDefinition beanDefinition = new XqcBeanDefinition();
                //获取bean名称
                String beanName = getBeanName(aClass);
                beanDefinition.setClazz(aClass);
                beanDefinition.setBeanName(beanName);
                //解析XqcScope注解
                if(aClass.isAnnotationPresent(XqcScope.class)){
                    XqcScope xqcScopeAnnotation = aClass.getAnnotation(XqcScope.class);
                    if(!ScopeEnum.PROTOTYPE.getCode().equals(xqcScopeAnnotation.value())
                            && !ScopeEnum.SINGLETON.getCode().equals(xqcScopeAnnotation.value())){
                        ThrowUtils.throwEx("bean scope类型不正确");
                    }
                    beanDefinition.setScope(xqcScopeAnnotation.value());
                }else{
                    //没有写默认单例
                    beanDefinition.setScope(ScopeEnum.SINGLETON.getCode());
                }

                //生成beanDefinition
                beanDefinitions.put(beanName,beanDefinition);

            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

    /**
     * 获取bean名称
     * @param aClass
     * @return
     */
    private String getBeanName(Class<?> aClass) {
        XqcComponent xqcComponentAnnotation = aClass.getAnnotation(XqcComponent.class);
        String beanName = xqcComponentAnnotation.value();
        if(beanName != null && !"".equals(beanName.trim())){
            return beanName;
        }
        //使用类名，首字母小写
        String simpleName = aClass.getSimpleName();
        return simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1, simpleName.length());
    }

    /**
     * 解析入口配置类，获取包路径
     * @param clazz
     * @return
     */
    private String getPackagePath(Class<?> clazz) {
        XqcConfiguration configurationAnnotation = clazz.getAnnotation(XqcConfiguration.class);
        //获取要扫描的路径
        String packages = configurationAnnotation.value();
        packages = packages.replace(".","/");
        //读取包下面的class,真正的Spring源码用的ASM技术，这里只是简单的实现读取
        ClassLoader classLoader = XqcApplicationContext.class.getClassLoader();
        return classLoader.getResource(packages).getFile();
    }

    /**
     * 获取类完整的路径，包名+类名
     * @param path
     * @return
     */
    private String subPath(String path) {
        path = path.substring(path.indexOf("classes") + 8, path.indexOf(".class")).replace("\\",".");
        return path;
    }

    /**
     * 扫描包路径
     * @param classes
     * @param path
     */
    private static void scannerPackage(List<String> classes, String path) {
        File file = new File(path);
        for (File file1 : file.listFiles()) {
            if (file1.isDirectory()) {
                scannerPackage(classes,file1.getPath());
            } else {
                classes.add(file1.getPath());
            }
        }
    }

    /**
     * 创建bean
     * @param beanName
     * @param beanDefinition
     * @return
     */
    public Object createBean(String beanName,XqcBeanDefinition beanDefinition){
        Object bean = null;
        try {
            Class clazz = beanDefinition.getClazz();
            bean = clazz.newInstance();

            //初始化前
            for (XqcBeanPostProcessor beanPostProcessor : beanPostProcessors) {
                bean = beanPostProcessor.postProcessBeforeInitialization(bean,beanName);
            }

            for (Field field : clazz.getDeclaredFields()) {
                //解析XqcValue注解
                if (field.isAnnotationPresent(XqcValue.class)) {
                    XqcValue xqcValueAnnotation = field.getAnnotation(XqcValue.class);
                    field.setAccessible(true);
                    field.set(bean, xqcValueAnnotation.value());
                }
                //解析XqcAutowired注解
                if (field.isAnnotationPresent(XqcAutowired.class)) {
                    Object bean1 = getBean(field.getType());
                    field.setAccessible(true);
                    field.set(bean, bean1);
                }
            }
            //解析aware接口
            if (bean instanceof XqcBeanAware) {
                ((XqcBeanAware) bean).setBeanName(beanName);
            }

            //解析XqcPostConstruct注解的方法
            for (Method method : clazz.getDeclaredMethods()) {
                //解析XqcValue注解
                if (method.isAnnotationPresent(XqcPostConstruct.class)) {
                    method.invoke(bean,  method.getTypeParameters());
                }
            }

            //bean初始化执行
            if (bean instanceof XqcInitializingBean) {
                ((XqcInitializingBean) bean).afterPropertiesSet();
            }

            //初始化后
            for (XqcBeanPostProcessor beanPostProcessor : beanPostProcessors) {
                bean = beanPostProcessor.postProcessAfterInitialization(bean, beanName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bean;
    }

    /**
     * 获取bean
     * @param beanName
     * @return
     */
    public Object getBean(String beanName){
        XqcBeanDefinition xqcBeanDefinition = beanDefinitions.get(beanName);
        if(xqcBeanDefinition == null){
            ThrowUtils.throwEx("bean对象不存在");
        }
        String scope = xqcBeanDefinition.getScope();
        if(ScopeEnum.PROTOTYPE.getCode().equals(scope)){
            //原型，每次都创建
            return this.createBean(beanName,beanDefinitions.get(beanName));
        }
        if(singletonObjects.containsKey(beanName)){
            return singletonObjects.get(beanName);
        }
        return this.createBean(beanName, beanDefinitions.get(beanName));
    }

    public Object getBean(Class clazz){
        return this.getBean(this.getBeanName(clazz));
    }

}
